package org.dmikis.task11;

import static java.lang.Math.abs;
import static java.lang.Math.cos;
import static java.lang.Math.sin;

import java.awt.GridLayout;
import java.awt.geom.Point2D;
import java.io.PrintWriter;
import java.util.ArrayList;

import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;

import org.dmikis.jmmengine.models.Model;
import org.dmikis.jmmengine.models.Result;
import org.dmikis.task11.Task11PhasePoints.PhasePoint;

public class Task11Model implements Model {
    
    public Task11Model() {
	this.points = new Task11PhasePoints();
    }

    @Override
    public void compute() {
	points.clearTrack();
	if (this.paramModelName == "Euler") {
	    this.computeEuler();
	}
	else if (this.paramModelName == "Runge-Kutta") {
	    this.computeRungeKutta();
	}
    }
    
    protected void computeEuler() {
	PhasePoint currentPoint = new PhasePoint(this.paramStartVelocity, this.paramStartTetta, 0.0);
	this.points.clear();
	this.points.add(currentPoint);
	double current_time = 0.0;
	double tmpV1, tmpV2, tmpTetta1, tmpTetta2;
	double curr_x = 0.0, curr_y = 0.0;
	while (current_time < 250.0) {
	    double timeStep = 2.0 * 0.01;
	    do {
		timeStep = 0.5 * timeStep;
		
		tmpV1 = currentPoint.getV() + evaluateDv(currentPoint.getV(), currentPoint.getTetta()) * timeStep;
        	tmpTetta1 = currentPoint.getTetta() + evaluateDtetta(currentPoint.getV(), currentPoint.getTetta()) * timeStep;
        	
        	tmpV2 = currentPoint.getV() + evaluateDv(currentPoint.getV(), currentPoint.getTetta()) * timeStep * 0.5;
        	tmpTetta2 = currentPoint.getTetta() + evaluateDtetta(currentPoint.getV(), currentPoint.getTetta()) * timeStep * 0.5;
        	tmpV2 += evaluateDv(tmpV2, tmpTetta2) * timeStep * 0.5;
        	tmpTetta2 += evaluateDtetta(tmpV2, tmpTetta2) * timeStep * 0.5;
	    } while ((abs(tmpV1 - tmpV2) > this.paramAccuracy) || (abs(tmpTetta1 - tmpTetta2) > this.paramAccuracy));
	    points.add(currentPoint = new PhasePoint(tmpV1, tmpTetta1, current_time += timeStep));
	    points.addTrack(new Point2D.Double(curr_x += tmpV1*cos(tmpTetta1)*timeStep, curr_y += tmpV1*sin(tmpTetta1)*timeStep));
	}
/*	double a1 = 0.0, a2 = 0.0;
	for (int i = 0; i < 100) {
	    
	}*/
	this.points.updatePlots();
	outputTrack();
    }
    
    protected void computeRungeKutta() {
	PhasePoint currentPoint = new PhasePoint(this.paramStartVelocity, this.paramStartTetta, 0.0);
	this.points.clear();
	this.points.add(currentPoint);
	double current_time = 0.0;
	double tmpV1, tmpV2, tmpTetta1, tmpTetta2;
	double curr_x = 0.0, curr_y = 0.0;
	while (current_time < 250.0) {
	    double timeStep = 2.0 * 0.01;
	    do {
		timeStep = 0.5 * timeStep;
		double m1, m2, m3, m4, k1, k2, k3, k4;
		m1 = timeStep*evaluateDtetta(currentPoint.getV(), currentPoint.getTetta());
		k1 = timeStep*evaluateDv(currentPoint.getV(), currentPoint.getTetta());
		m2 = timeStep*evaluateDtetta(currentPoint.getV() + 0.5*m1, currentPoint.getTetta() + 0.5*k1);
		k2 = timeStep*evaluateDv(currentPoint.getV() + 0.5*m1, currentPoint.getTetta() + 0.5*k1);
		m3 = timeStep*evaluateDtetta(currentPoint.getV() + 0.5*m2, currentPoint.getTetta() + 0.5*k2);
		k3 = timeStep*evaluateDv(currentPoint.getV() + 0.5*m2, currentPoint.getTetta() + 0.5*k2);
		m4 = timeStep*evaluateDtetta(currentPoint.getV() + m3, currentPoint.getTetta() + k3);
		k4 = timeStep*evaluateDv(currentPoint.getV() + m3,currentPoint.getTetta() + k3);
		tmpV1 = currentPoint.getV() + (k1+k2+k3+k4)/6.0;
		tmpTetta1 = currentPoint.getTetta() + (m1+m2+m3+m4)/6.0;
		m1 = 0.5*timeStep*evaluateDtetta(currentPoint.getV(), currentPoint.getTetta());
		k1 = 0.5*timeStep*evaluateDv(currentPoint.getV(), currentPoint.getTetta());
		m2 = 0.5*timeStep*evaluateDtetta(currentPoint.getV() + 0.5*m1, currentPoint.getTetta() + 0.5*k1);
		k2 = 0.5*timeStep*evaluateDv(currentPoint.getV() + 0.5*m1, currentPoint.getTetta() + 0.5*k1);
		m3 = 0.5*timeStep*evaluateDtetta(currentPoint.getV() + 0.5*m2, currentPoint.getTetta() + 0.5*k2);
		k3 = 0.5*timeStep*evaluateDv(currentPoint.getV() + 0.5*m2, currentPoint.getTetta() + 0.5*k2);
		m4 = 0.5*timeStep*evaluateDtetta(currentPoint.getV() + m3, currentPoint.getTetta() + k3);
		k4 = 0.5*timeStep*evaluateDv(currentPoint.getV() + m3,currentPoint.getTetta() + k3);
		tmpV2 = currentPoint.getV() + (k1+k2+k3+k4)/6.0;
		tmpTetta2 = currentPoint.getTetta() + (m1+m2+m3+m4)/6.0;
		m1 = 0.5*timeStep*evaluateDtetta(currentPoint.getV(), currentPoint.getTetta());
		k1 = 0.5*timeStep*evaluateDv(currentPoint.getV(), currentPoint.getTetta());
		m2 = 0.5*timeStep*evaluateDtetta(currentPoint.getV() + 0.5*m1, currentPoint.getTetta() + 0.5*k1);
		k2 = 0.5*timeStep*evaluateDv(currentPoint.getV() + 0.5*m1, currentPoint.getTetta() + 0.5*k1);
		m3 = 0.5*timeStep*evaluateDtetta(currentPoint.getV() + 0.5*m2, currentPoint.getTetta() + 0.5*k2);
		k3 = 0.5*timeStep*evaluateDv(currentPoint.getV() + 0.5*m2, currentPoint.getTetta() + 0.5*k2);
		m4 = 0.5*timeStep*evaluateDtetta(currentPoint.getV() + m3, currentPoint.getTetta() + k3);
		k4 = 0.5*timeStep*evaluateDv(currentPoint.getV() + m3,currentPoint.getTetta() + k3);
		tmpV2 += (k1+k2+k3+k4)/6.0;
		tmpTetta2 += (m1+m2+m3+m4)/6.0;
	    } while ((abs(tmpV1 - tmpV2) > this.paramAccuracy) || (abs(tmpTetta1 - tmpTetta2) > this.paramAccuracy));
	    points.add(currentPoint = new PhasePoint(tmpV1, tmpTetta1, current_time += timeStep));
	    points.addTrack(new Point2D.Double(curr_x += tmpV1*cos(tmpTetta1)*timeStep, curr_y += tmpV1*sin(tmpTetta1)*timeStep));
	}
	this.points.updatePlots();
	outputTrack();
    }
    
    protected void outputTrack() {
	ArrayList<Point2D.Double> track = this.points.getTrack();
	try {
	    PrintWriter out = new PrintWriter("track.txt");
	    //double curr_x = 0.0, curr_y = 0.0;
	    //out.printf("%f\t%f\n", curr_x, curr_y);
	    for (Point2D.Double point : track) {
		out.printf("%f\t%f\n", point.x, point.y);
	    }
	    out.close();
	}
	catch (Exception e) {
	    e.printStackTrace();
	}
    }
    
    protected double evaluateDv(double currV, double currTetta) {
	return -sin(currTetta) - this.paramA;
    }
    
    protected double evaluateDtetta(double currV, double currTetta) {
	return currV - cos(currTetta) / currV;
    }

    @Override
    public int getStatus() {
	return 0;
    }

    @Override
    public JPanel getParametersPanel() {
	return this.parameters;
    }

    @Override
    public void renewModelParameters() throws IllegalArgumentException {
	this.paramModelName = (String) this.parameters.paramModelsList.getSelectedItem();
	this.paramA = Double.parseDouble(this.parameters.paramA.getText());
	this.paramStartTetta = Double.parseDouble(this.parameters.paramStartTetta.getText());
	this.paramStartVelocity = Double.parseDouble(this.parameters.paramStartVelocity.getText());
	this.paramAccuracy = Double.parseDouble(this.parameters.paramAccuracy.getText());
    }

    @Override
    public Result getResult() {
	return this.points;
    }
    
    @SuppressWarnings("serial")
    protected class ParametersPanel extends JPanel {
	
	public ParametersPanel() {
	    super(new GridLayout(5, 2));
	    add(new JLabel("Model:"));
	    add(this.paramModelsList = new JComboBox());
	    this.paramModelsList.addItem("Euler");
	    this.paramModelsList.addItem("Runge-Kutta");
	    add(new JLabel("A:"));
	    add(this.paramA = new JTextField(Double.toString(DEFAULT_A)));
	    add(new JLabel("Start velocity:"));
	    add(this.paramStartVelocity = new JTextField(Double.toString(DEFAULT_VELOCITY)));
	    add(new JLabel("Start tetta:"));
	    add(this.paramStartTetta = new JTextField(Double.toString(DEFAULT_TETTA)));
	    add(new JLabel("Accuracy:"));
	    add(this.paramAccuracy = new JTextField(Double.toString(DEFAULT_ACCURACY)));
	}
	
	protected JComboBox paramModelsList;
	protected JTextField paramA;
	protected JTextField paramStartTetta;
	protected JTextField paramStartVelocity;
	protected JTextField paramAccuracy;
    }
    
    protected static final double DEFAULT_A = 0.00001;
    protected static final double DEFAULT_TETTA = 0.25*Math.PI;
    protected static final double DEFAULT_VELOCITY = 1.0;
    protected static final double DEFAULT_ACCURACY = 0.01;
    
    protected String paramModelName = "Euler";
    protected double paramA = DEFAULT_A;
    protected double paramStartTetta = DEFAULT_TETTA;
    protected double paramStartVelocity = DEFAULT_VELOCITY;
    protected double paramAccuracy = DEFAULT_ACCURACY;
    
    protected volatile Task11PhasePoints points;
    protected ParametersPanel parameters = new ParametersPanel();
}
